package com.neuronbit.businessflow.runtime;

import com.neuronbit.businessflow.runtime.exec.*;
import com.neuronbit.businessflow.runtime.repo.BusinessFlowRepository;

import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;

public class JdbcBusinessFlowRepository implements BusinessFlowRepository {
    private final DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
    private final DataSource dataSource;

    public JdbcBusinessFlowRepository(DataSource dataSource) {
        this.dataSource = dataSource;
    }

    @Override
    public ExecutedFlowInstance findExecutedFlowInstance(String flowTypeName, String id) throws SQLException {
        Connection connection = dataSource.getConnection();
        try {
            PreparedStatement preparedStatement = connection.prepareStatement("select * from flow_execution" +
                    " where flow_id = ? and flow_type_name = ?");
            preparedStatement.setString(1, id);
            preparedStatement.setString(2, flowTypeName);
            ResultSet resultSet = preparedStatement.executeQuery();
            if (resultSet.next()) {
                ExecutedFlowInstance instance = new ExecutedFlowInstance();
                instance.setExecutionId(resultSet.getString("execution_id"));
                instance.setFlowId(resultSet.getString("flow_id"));
                instance.setFlowName(resultSet.getString("flow_name"));
                instance.setFlowTypeName(resultSet.getString("flow_type_name"));
                instance.setReturnTypeName(resultSet.getString("return_type_name"));
                instance.setReturns(resultSet.getString("returns"));
                instance.setContextTypeName(resultSet.getString("context_type_name"));
                instance.setContext(resultSet.getString("context"));
                instance.setStatus(FlowExecutionStatus.valueOf(resultSet.getString("status")));
                instance.setStartTime(LocalDateTime.parse(resultSet.getString("start_time"), formatter));
                instance.setEndTime(LocalDateTime.parse(resultSet.getString("end_time"), formatter));
                instance.setErrorMessage(resultSet.getString("error_message"));
                return instance;
            } else {
                return null;
            }
        } finally {
            connection.close();
        }
    }

    @Override
    public void initAndLock(ExecutedFlowInstance instance) throws SQLException {
        Connection connection = dataSource.getConnection();
        try {
            boolean autoCommit = connection.getAutoCommit();
            PreparedStatement preparedStatement = connection.prepareStatement("insert into flow_execution" +
                    "(execution_id,flow_id,flow_name,flow_type_name,return_type_name,returns,context_type_name,context,status,start_time,error_message,lock_status)" +
                    "values(?,?,?,?,?,?,?,?,?,?,?,?)");
            preparedStatement.setString(1, instance.getExecutionId());
            preparedStatement.setString(2, instance.getFlowId());
            preparedStatement.setString(3, instance.getFlowName());
            preparedStatement.setString(4, instance.getFlowTypeName());
            preparedStatement.setString(5, instance.getReturnTypeName());
            preparedStatement.setString(6, instance.getReturns() == null ? "" : instance.getReturns());
            preparedStatement.setString(7, instance.getContextTypeName());
            preparedStatement.setString(8, instance.getContext());
            preparedStatement.setString(9, instance.getStatus().name());
            preparedStatement.setString(10, formatter.format(instance.getStartTime()));
            preparedStatement.setString(11, instance.getErrorMessage());
            preparedStatement.setString(12, instance.getLockStatus());
            preparedStatement.execute();
            if (!autoCommit) {
                connection.commit();
            }
        } finally {
            connection.close();
        }
    }

    @Override
    public void updateFlowExecutionByExecutionId(ExecutedFlowInstance instance, FlowExecutionStatus originalStatus, LockStatus originalLockStatus) throws SQLException {
        Connection connection = dataSource.getConnection();
        try {
            boolean autoCommit = connection.getAutoCommit();
            PreparedStatement preparedStatement = connection.prepareStatement("update flow_execution set" +
                    " status=?, end_time=?, error_message = ?, context=?, lock_status=?" +
                    " where execution_id=? and status=? and lock_status=?");
            preparedStatement.setString(1, instance.getStatus().name());
            preparedStatement.setString(2, formatter.format(instance.getEndTime()));
            preparedStatement.setString(3, instance.getErrorMessage());
            preparedStatement.setString(4, instance.getContext());
            preparedStatement.setString(5, instance.getLockStatus());
            preparedStatement.setString(6, instance.getExecutionId());
            preparedStatement.setString(7, originalStatus.name());
            preparedStatement.setString(8, originalLockStatus.name());
            preparedStatement.execute();
            if (!autoCommit) {
                connection.commit();
            }
        } finally {
            connection.close();
        }
    }

    @Override
    public void saveStepExecution(ExecutedStepInstance instance) throws SQLException {
        Connection connection = dataSource.getConnection();
        try {
            boolean autoCommit = connection.getAutoCommit();
            PreparedStatement preparedStatement = connection.prepareStatement("insert into step_execution" +
                    "(execution_id,step_id,step_name,step_type_name,returns,context_type_name,context,status,start_time,error_message)" +
                    "values(?,?,?,?,?,?,?,?,?,?)");
            preparedStatement.setString(1, instance.getExecutionId());
            preparedStatement.setString(2, instance.getStepId());
            preparedStatement.setString(3, instance.getStepName());
            preparedStatement.setString(4, instance.getStepTypeName());
            preparedStatement.setString(5, instance.getReturns() == null ? "" : instance.getReturns());
            preparedStatement.setString(6, instance.getContextTypeName());
            preparedStatement.setString(7, instance.getContext());
            preparedStatement.setString(8, instance.getStatus().name());
            preparedStatement.setString(9, formatter.format(instance.getStartTime()));
            preparedStatement.setString(10, instance.getErrorMessage());
            preparedStatement.execute();
            if (!autoCommit) {
                connection.commit();
            }
        } finally {
            connection.close();
        }
    }

    @Override
    public void updateStepExecutionByExecutionId(ExecutedStepInstance instance, StepExecutionStatus originalStatus) throws SQLException {
        Connection connection = dataSource.getConnection();
        try {
            boolean autoCommit = connection.getAutoCommit();
            PreparedStatement preparedStatement = connection.prepareStatement("update step_execution set" +
                    " status=?, end_time=?, error_message = ? where execution_id=? and step_id=? and status=?");
            preparedStatement.setString(1, instance.getStatus().name());
            preparedStatement.setString(2, formatter.format(instance.getEndTime()));
            preparedStatement.setString(3, instance.getErrorMessage());
            preparedStatement.setString(4, instance.getExecutionId());
            preparedStatement.setString(5, instance.getStepId());
            preparedStatement.setString(6, originalStatus.name());
            preparedStatement.execute();
            if (!autoCommit) {
                connection.commit();
            }
        } finally {
            connection.close();
        }
    }
}
